/*
 *  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
 *  reserved.
 *
 *  Redistribution and use in source and binary forms, with or withouta
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following discalimer in
 *  the documentation and/or other materials provided with the
 *  distribution.
 *
 *  3. The end-user documentation included with the redistribution,
 *  if any, must include the following acknowledgment:
 *  "This product includes software developed by the
 *  Sun Microsystems, Inc. for Project JXTA."
 *  Alternately, this acknowledgment may appear in the software itself,
 *  if and wherever such third-party acknowledgments normally appear.
 *
 *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *  must not be used to endorse or promote products derived from this
 *  software without prior written permission. For written
 *  permission, please contact Project JXTA at http://www.jxta.org.
 *
 *  5. Products derived from this software may not be called "JXTA",
 *  nor may "JXTA" appear in their name, without prior written
 *  permission of Sun.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *  ====================================================================
 *
 *  This software consists of voluntary contributions made by many
 *  individuals on behalf of Project JXTA.  For more
 *  information on Project JXTA, please see
 *  <http://www.jxta.org/>.
 *
 *  This license is based on the BSD license adopted by the Apache Foundation.
 *
 *  $Id: MyJXTA.java,v 1.109 2007/06/10 21:15:10 nano Exp $
 */

package net.jxta.myjxta;
import net.jxta.endpoint.Message;
import net.jxta.exception.PeerGroupException;
import net.jxta.ext.config.ConversionException;
import net.jxta.logging.Logging;
import net.jxta.myjxta.dialog.*;
import net.jxta.myjxta.plugin.DefaultMyJxtaPluginContainer;
import net.jxta.myjxta.plugin.PluginContainer;
import net.jxta.myjxta.plugin.PluginView;
import net.jxta.myjxta.presence.PeerEntry;
import net.jxta.myjxta.presence.PresenceController;
import net.jxta.myjxta.search.SearchManager;
import net.jxta.myjxta.search.SearchModifier;
import net.jxta.myjxta.search.Searcher;
import net.jxta.myjxta.share.IShareManager;
import net.jxta.myjxta.share.IShareManagerProvider;
import net.jxta.myjxta.ui.ChatFrame;
import net.jxta.myjxta.util.*;
import net.jxta.myjxta.util.configuration.MyJXTAConfigurator;
import net.jxta.myjxta.util.configuration.NetworkManagerConfigurator;
import net.jxta.myjxta.util.objectmodel.GroupNode;
import net.jxta.myjxta.util.objectmodel.JxtaNode;
import net.jxta.myjxta.util.objectmodel.MyJxtaObjectRepository;
import net.jxta.myjxta.util.objectmodel.PeerNode;
import net.jxta.myjxta.util.preferences.MyPreferenceManager;
import net.jxta.peergroup.NetPeerGroupFactory;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.pipe.PipeService;
import net.jxta.protocol.PeerGroupAdvertisement;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.rendezvous.RendezVousService;
import net.jxta.rendezvous.RendezvousEvent;
import net.jxta.rendezvous.RendezvousListener;
import net.jxta.util.JxtaBiDiPipe;

import javax.management.*;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author james todd [gonzo at jxta dot org]
 * @version $Id: MyJXTA.java,v 1.109 2007/06/10 21:15:10 nano Exp $
 */

public final class MyJXTA implements GroupListener, RendezvousListener {

    public static final String GROUP_ID = "group.id";

    public static final String GROUP_NAME = "group.name";

    public static final String GROUP_DESCRIPTION = "group.description";

    public static final String GROUP_AUTO_RENDEZVOUS_PERIOD = "group.autoRendezVousPeriod";

    private static final ResourceBundle STRINGS = Resources.getStrings();

    // not used right now --> commented out
    // private static final int CONFIGURATION_WAIT = 50;

    private static final Logger LOG = Logger.getLogger(MyJXTA.class.getName());

    private final ArrayList<Group> joinedGroups = new ArrayList<Group>();

    private String peerName = null;

    /**
     * there are some plugins (CMS) that needs a call to "System.exit(0)"....
     */
    public final boolean m_needsSystemExitOnShutdown = false;

    /**
     * The UI to use to display the data
     */
    private View view = null;

    /**
     * The PreferenceManager object to use
     */
    private MyPreferenceManager myPreferenceManager = null;

    public MultipleInstanceUtil.AdministrationListener administrationListener = null;

    private PluginContainer m_pluginContainer;

    private ArrayList<String> administrationBuffer = null;

    public static final String NAME = Constants.getInstance().get(
            Constants.NAME);

    public static final String VERSION = NAME + " "
            + Constants.getInstance().get(Constants.VERSION) + " ["
            + Constants.getInstance().get(Constants.BUILD) + "]";
    private RendezVousService rendezvousNPG;
    private final static Object networkConnectLock = new Object();


    public static MyJXTA getTheInstance() {
        return g_theInstance;
    }

    private static MyJXTA g_theInstance;

    public static MyJXTA startMyJxta() {
        if (g_theInstance == null) {
            g_theInstance = new MyJXTA();
        }
        return g_theInstance;
    }

    /**
     * Create a new instance of MyJxta and initializes it. This starts the
     * JXTA network if it was not already started
     */
    private MyJXTA() {

        // only one Instance of MyJxta can be active inside the same working
        // directory
        if (!MultipleInstanceUtil.isOnlyInstance(this)) {
            destroy();
            return;
        }

        g_theInstance = this;

        // only needed for the NetBean integration
        registerBeanStuff();

        // initiate the Plugin / Module Container (will instantiate the plugins,
        // modules...)
        m_pluginContainer = new DefaultMyJxtaPluginContainer(this);

        // ViewFactory.setViewerClass(net.jxta.myjxta.ui.MyJXTALiteViewer.class);
        // initiate the view (right now only a single swing view is
        // implememented)
        this.view = ViewFactory.getView(this);

        //call the init methods of the plugins
        m_pluginContainer.init();


        this.view.setVisible(true);

//        boolean isConfigured = configureMyJxta();

        try {

            MyJXTAConfigurator c = new NetworkManagerConfigurator(this.view);
            //MyJXTAConfigurator c = new JxtaExtConfigurator(this.view);
            c.configurePlatform();
            initialize();
        } catch (IOException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            view.showMessageDialog(e.getLocalizedMessage());
        }
    }


    private void registerBeanStuff() {
        try {
            ManagementFactory.getPlatformMBeanServer().registerMBean(
                    Status.getInstance(),
                    new ObjectName(":type=net.jxta.myjxta"));
        } catch (MalformedObjectNameException mone) {
            mone.printStackTrace();
        } catch (InstanceAlreadyExistsException iaee) {
            iaee.printStackTrace();
        } catch (MBeanRegistrationException mbre) {
            mbre.printStackTrace();
        } catch (NotCompliantMBeanException ncmbe) {
            ncmbe.printStackTrace();
        }
    }

    public MyPreferenceManager getPreferenceManager() {
        return this.myPreferenceManager;
    }

    /**
     * Return the name of the peer
     *
     * @return the name of the peer
     */

    public String getPeerName() {
        return this.peerName;
    }

    /**
     * Add a dialog that should be displayed in the UI
     *
     * @param dialog the dialog to display
     */
    public void addDialog(Dialog dialog) {
        this.view.addDialog(dialog);
    }

    public void removeDialog(Dialog dialog) {
        this.view.removeDialog(dialog);
    }

    /**
     * Return the ShareManager instance used for file sharing
     *
     * @return the ShareManager instance used for file sharing
     */
    public IShareManager getShareManager() {
        IShareManagerProvider iShareManagerProvider = getShareManagerProvider();
        if (iShareManagerProvider == null)
            return null;  //no provider (no plugin) --> no shareManager!

        return iShareManagerProvider.getShareManager(this, this.view.getGroup(), true);
    }

    private IShareManagerProvider getShareManagerProvider() {
        IShareManagerProvider shareManagerProvider = (IShareManagerProvider) getPluginContainer().getPlugin(IShareManagerProvider.class);
        if (shareManagerProvider == null)
            return null;
        else
            return shareManagerProvider;
    }

    /**
     * Part of group Lifecycle. Joins group. Starts group services and
     * associated resources.
     */
    public void joinGroup(final Group group, boolean useAutoRdvMode,
                          boolean discover) {

        final PeerGroup peerGroup = group.getPeerGroup();
        setStatus(STRINGS.getString("status.group.join") + " "
                + peerGroup.getPeerGroupName());

        this.view.setTitle(peerGroup.getPeerName() + "@"
                + peerGroup.getPeerGroupName());


        setStatus(STRINGS.getString("status.group.display") + " "
                + peerGroup.getPeerGroupName());


        String dialogName = Dialog.getDialogNamer(OneToOneCommandDialog.class)
                .getDialogName(peerGroup.getPeerName());
        PipeAdvertisement pipeAdvertisment = PipeUtil.getAdv(peerGroup,
                dialogName, PipeService.UnicastType, null);

        // now lets add the different dialogs/pipe listeners
        if (group.isVisible()) {
            registerCommandDialogPipeListener(group);
        }


        // add the one Group Panel to the navigation tree
        if (group.isVisible()) {
            view.createGroupNavigation(group);
        }

        GroupNode groupNode = new GroupNode(group);
        if (group.getParentGroup() != null) {
            groupNode.setParent(new GroupNode(group.getParentGroup()));
        } else {
            groupNode.setParent(MyJxtaObjectRepository.getRoot());
        }
        addJxtaNode(groupNode);
        PeerNode ownPeerNode = new PeerNode(new Peer(pipeAdvertisment), group);
        group.setOwnCommandId(pipeAdvertisment.getPipeID());
        addJxtaNode(ownPeerNode, true);
        restore(group);

        if (discover) {
            discover(group);
        }

        addToJoinedGroups(group);

        if (group.isVisible()) {
            PeerGroup cpg = AuthenticationUtil.getTLSPeerGroup(peerGroup);

            if (!AuthenticationUtil.isAuthenticated(cpg)) {
                if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
                    LOG.info("authenticating");
                }

                AuthenticationUtil.authenticate(getView(), cpg);
            }

            if (!AuthenticationUtil.isAuthenticated(cpg)) {
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("not authenticated");
                }
            }
        }

        // tell the plugins that we have joined a group
        m_pluginContainer.notifyAboutGroupJoin(group);



        if (useAutoRdvMode) {
            if (shouldBeAutoRdv(group)) {
                setStatus(STRINGS.getString("status.group.rendezvous.auto")
                        + ": " + group.getAutoRendezVousPeriod());
                peerGroup.getRendezVousService().setAutoStart(useAutoRdvMode,
                        group.getAutoRendezVousPeriod());
            }
        }
    }

    private void registerCommandDialogPipeListener(final Group group) {
        String dialogName;
        // START 1to1Command
        setStatus(STRINGS.getString("status.dialog.command.register"));
        dialogName = Dialog.getDialogNamer(OneToOneCommandDialog.class)
                .getDialogName(group.getPeerGroup().getPeerName());
        setStatus(STRINGS.getString("status.dialog.listener.add") + ": "
                + dialogName);

        DialogManager.getInstance(group, dialogName, PipeService.UnicastType).addPipeListener(group.getPeerGroup(),
                new DialogPipeListener() {

                    public void receive(@SuppressWarnings(value = "unused")
                    PeerGroup pg,
                                        JxtaBiDiPipe pipe) {
                        // install a local command listener at the
                        // incomming pipe
                        // (each module registers its specific commands
                        // via CommandFactory.
                        DialogManager.installCommandListener(OneToOneCommandDialog.class,
                                group,
                                pipe,
                                MyJXTA.this);
                    }

                    public void receive(PeerGroup pg,
                                        Message msg) {
                        // commands are transported via BiDiPipes only -
                        // no need to handle incomming messages
                        LOG.warning("should never happen!" +
                                pg +
                                msg);
                    }
                });

        // END ONE2ONE Command
    }

    private void addToJoinedGroups(Group p_group) {
        joinedGroups.add(p_group);
    }

    private void removeFromJoinedGroups(Group p_group) {
        joinedGroups.remove(p_group);
    }

    /**
     * has this peer the needed tcp-ip connectivity to act as an auto-rdv?
     *
     * @param p_group
     * @return true if the peer should be configured for auto-rdv behaviour
     */
    private boolean shouldBeAutoRdv(Group p_group) {
        if (p_group.isConnected()) {
            // comments on this one? are proxyed peers able to act as a rdv?
            String proxyHost = System.getProperty(Env.HTTP_PROXY_HOST);
            return proxyHost == null || proxyHost.trim().length() == 0;
        }
        return true; // not connected -->enable auto-rdv (not an optimal
        // solution,
        // because we may connect later, but the only one that can prevent
        // complete rdv-less groups right now)
    }

    /**
     * Part of group Lifecycle. Resigns from group. Dismesses all group
     * resources.
     */
    public void resignGroup(GroupNode groupNode) {
        Group g = groupNode.getGroup();
        boolean resigned = resignGroup(g);
        if (resigned) {
            // and update the JxtaTree
            for (Iterator n = groupNode.getChildren(); n.hasNext();) {
                removeJxtaNode((JxtaNode) n.next());
            }

            if (g.isVisible()) {
                view.removeGroupNavigation(g);
            }
        }

    }

    private boolean resignGroup(Group g) {

        setStatus(STRINGS.getString("status.group.resign") + " " + g.getName());

        // first notify the plugins that we are leaving a group
        m_pluginContainer.notifyAboutGroupResign(g);

        // now lets remove the core modules und listeners that are no longer
        // needed
        // in this group
        if (g.isJoined()) {
            PeerGroup peerGroup = g.getPeerGroup();
            String peerNameInGroup = peerGroup.getPeerName();
            String pn = Dialog.getDialogNamer(OneToOneCommandDialog.class)
                    .getDialogName(peerNameInGroup);

            setStatus(STRINGS.getString("status.dialog.listener.remove") + ": "
                    + pn);

            DialogManager.getInstance(g, pn, PipeService.UnicastType)
                    .clearPipeListeners(peerGroup);

            setStatus(STRINGS.getString("status.dialog.clear") + ": "
                    + g.getName());


            List<Dialog> dialogsInGroup = DialogManager.getDialogsForGroup(g);
            if (dialogsInGroup != null) {
                for (Dialog d : dialogsInGroup) {
                    removeDialog(d);
                }
            }

            // xxx: sould the following call remove all of the above
            // pipe listeners
            DialogManager.clear(g);


            SearchManager.getInstance().destoy();
            // LOG.error(g.getName()+" - sharemanger stopped");
            // and finally resign
            try {
                // xxx: shouldn't group.resign() do this
                peerGroup.getMembershipService().resign();
                g.resign(false);
                // LOG.error(g.getName()+" - resign done");
            } catch (PeerGroupException pge) {
                String msg = "Error resigning from " + g.getName() + ": "
                        + pge.getMessage();

                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, msg, pge);
                }
                setStatus(msg);
            }

            if (!g.isJoined()) {
                // todo: clarify: we can call removeFromJoinedGroups from within
                // a Object repository listener,
                // but due to the fact that the repository removes all dependend
                // nodes recursivly this
                // will not be accurate (sub groups would also be removed, but
                // they are still joined?)
                removeFromJoinedGroups(g);

                // xxx: hack
                Group parentGroup = g.getParentGroup();
                if (this.view != null && parentGroup != null) {
                    this.view.setTitle(parentGroup.getName());
                }
            }
        }
        return !g.isJoined();
    }

    /**
     * Add a new JxtaNode object to the displayed tree. View takes care to
     * not put in nodes twice
     *
     * @param node the node to display
     */
    public void addJxtaNode(JxtaNode node) {
        addJxtaNode(node, false);
    }

    public void addJxtaNode(JxtaNode node, boolean select) {
        this.view.addJxtaNode(node, select);
        JxtaNode parent = node.getParent();
        if (parent == null) {
            parent = node.getJxtaNode().getParent();
        }


        boolean added= MyJxtaObjectRepository.getObjectRepository().add(node, parent);
        if (added && node instanceof PeerNode) {
            PeerNode peerNode = (PeerNode) node;
            if (peerNode.getParent().getGroup().isVisible()){
                PresenceController controller = PresenceController.getController(peerNode.getParent().getGroup().getPeerGroup());
                if (controller!=null){
                    controller.addEntry(new PeerEntry(peerNode),this);
                }
            }
        }
    }

    public void removeJxtaNode(JxtaNode p_jxtaNode) {
        view.removeJxtaNode(p_jxtaNode);
        MyJxtaObjectRepository.getObjectRepository().remove(p_jxtaNode);
    }

    public void addListener(DialogListener listener) {
        boolean isEmpty = true;

        for (PluginView pluginView : this.view.getPluginPanels()) {
            pluginView.getDialog().addListener(listener);

            isEmpty = false;
        }

        if (isEmpty) {
            if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
                LOG.severe("PluginView Plugin Collection is empty");
            }
        }
    }

    public void removeListener(DialogListener listener) {
        for (PluginView pluginView : this.view.getPluginPanels()) {
            pluginView.getDialog().removeListener(listener);
        }
    }

    public void addInboundFilter(DialogFilter filter) {
        for (PluginView pluginView : this.view.getPluginPanels()) {
            pluginView.getDialog().getInboundFilters().addFilter(filter);
        }
    }

    public void removeInboundFilter(Class dialogFilterClass) {
        for (PluginView pluginView : this.view.getPluginPanels()) {
            pluginView.getDialog().getInboundFilters().removeFilter(
                    dialogFilterClass);
        }
    }

    public void addOutboundFilter(DialogFilter filter) {
        for (PluginView pluginView : this.view.getPluginPanels()) {
            pluginView.getDialog().getOutboundFilters().addFilter(filter);
        }
    }

    public void removeOutboundFilter(Class<?> dialogFilterClass) {
        for (PluginView pluginView : this.view.getPluginPanels()) {
            pluginView.getDialog().getOutboundFilters().removeFilter(
                    dialogFilterClass);
        }
    }

    public View getView() {
        return this.view;
    }

    /**
     * Application shutdown
     */
    public void destroy() {
        new Thread("DestroyMyJxta Thread") {

            public synchronized void start() {
                setDaemon(true);
                super.start();
            }

            public void run() {
                try {
                    int joinedCount = joinedGroups.size();

                    for (int i = joinedCount - 1; i >= 0; i--) {
                        Group g = joinedGroups.get(i);

                        resignGroup(g);
                    }
                    if (joinedGroups.size() > 0) {
                        LOG.severe("did not leave all groups cleanly!");
                    }
                    try {
                        if (m_pluginContainer != null) {
                            m_pluginContainer.destroy();
                        }
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (MyJXTA.this.administrationListener != null) {
                        MyJXTA.this.administrationListener.interrupt();
                        Env.delete(new File(Env.getHomeDirPath() +
                                File.separator +
                                Env.ADMINISTRATION_PORT));
                    }
                    view.disposeView();
                    MyPreferenceManager.getInstance().shutdown();
                    administrationListener.stopListener();
                }
                catch (Throwable e) {
                    e.printStackTrace();
                }
                finally {
                    // nano: i would like to avoid this call - but CMS has a bug and will never close one
                    // of his MessageProcessor Threads
                    // @see net.jxta.share.MessageProcessor.run(MessageProcessor.java:105)
                    Thread hardTerminateThread = new Thread("Hard Terminate after Timeout Thread") {

                        public void run() {
                            try {
                                sleep(30000);
                                System.out.println("normal shutdown failed - will exit via System.exit(0) call");
                                System.exit(0);
                            } catch (InterruptedException e) {
                                //nothing
                            }
                        }
                    };
                    hardTerminateThread.setDaemon(true);
                    hardTerminateThread.start();
                }
            }
        }.start();
    }

    /**
     * Changes the status information that is displayed in the ui.
     *
     * @param status the new status information to display
     */
    public void setStatus(String status) {
        if (status != null) {
            this.view.setStatus(status);
            Status.getInstance().statusEvent(status);
        }
    }

    public void groupEvent(Group group, String status, @SuppressWarnings("unused")
    EventObject p_re) {
        if (status != null) {
            setStatus(group.getName() + ":" + status);
        }

        if (Group.CONNECT.equals(status)) {
            // xxx: check to see if group is discoverable
            discover(group);
        }
        if (group.isVisible()) { // do nothing for the invisible npg
            getPluginContainer().updateGroupState(group);
        }

    }

    /**
     * Initializes the MyJxta instance
     */
    private void initialize() {
        configureHTTPProxy();

        Group npgWrapper = null;

        setStatus(STRINGS.getString("status.jxta.startNetPeerGroup"));

        try {
            PeerGroup netPeerGroup = new NetPeerGroupFactory().getInterface();
            npgWrapper = new Group(this, netPeerGroup, null, false);
        } catch (PeerGroupException pge) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", pge);
            }
        }

        if (npgWrapper == null) {
            setStatus(STRINGS.getString("error.jxta.start"));
            return;

        }
        this.peerName = npgWrapper.getPeerGroup().getPeerName();
        // this.preferencesManager = new PreferenceManager(this);
        this.myPreferenceManager = MyPreferenceManager.getInstance();

        setStatus(STRINGS.getString("status.jxta.joiningApplicationGroup"));

        String gid = Constants.getInstance().get(Constants.GROUP_ID, "").trim();

        final boolean mainGroupIsNetPeerGroup = gid.length() == 0;

        // the normal usecase is that we are running in a dedicated myjxta
        // group... in this usecase the netpeergroup is not important for the user 
        // and therefore should not be visible.
        // however, if the NPG is our application group we want it to be visible
        npgWrapper.setVisible(mainGroupIsNetPeerGroup);

        joinGroup(npgWrapper, false, false);

        rendezvousNPG = npgWrapper.getPeerGroup().getRendezVousService();
        rendezvousNPG.addListener(this);
        waitForRendezvousConnection(5000);
        initializeGroup(npgWrapper);
        seedGroup(this.view.getGroup());
        myPreferenceManager.init();

        // this.preferencesManager.presetPreferences();

    }

    private void configureHTTPProxy() {
        Constants c = Constants.getInstance();
        URL hp = null;

        try {
            hp = c.getURL(Constants.PROXY_HTTP);
        } catch (ConversionException ce) {
            if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
                LOG.fine("unable to get proxy url: " +
                        c.get(Constants.PROXY_HTTP));
            }
        }

        System.setProperty(Env.HTTP_PROXY_HOST, hp != null ? hp.getHost() : "");
        System.setProperty(Env.HTTP_PROXY_PORT, hp != null ? String.valueOf(hp
                .getPort()) : "");
    }

    private void initializeGroup(Group parent) {
        initializeGroup(parent, Constants.GROUP, "/@id");
    }

    private void initializeGroup(Group parent, String queryPrefix,
                                 String queryPostfix) {
        String prefix = queryPrefix != null ? queryPrefix.trim() : null;
        String postfix = queryPostfix != null ? queryPostfix.trim() : null;

        if (prefix != null && postfix != null) {
            Constants c = Constants.getInstance();

            for (Object o : c.getAll(prefix + postfix)) {
                String gid = ((String) o).trim();

                if (gid.length() > 0) {
                    String q = prefix + "[@id=\"" + gid + "\"]";
                    String gn = c.get(q + "/@name", Defaults.GROUP_NAME);
                    String gp = c.get(q + "/@password", null);
                    String gd = c.get(q + "/@description",
                            Defaults.GROUP_DESCRIPTION);
                    String gar = c.get(q + "/@autoRendezVousPeriod", String
                            .valueOf(Constants.AUTO_RENDEZVOUS_JOIN_PERIOD));

                    setStatus(STRINGS.getString("status.group.create") + " "
                            + gn);

                    Group g = createGroupObject(parent, gn, gd, gp, gid);

                    if (g != null) {
                        joinGroupWithAutoIntervall(gar, g);
                        initializeGroup(g, q, "/group/@id"); // recursive
                        // call (we can
                        // automatically
                        // join inner
                        // groups)
                    } else {
                        setStatus(STRINGS.getString("error.group.create") + " "
                                + gn);
                    }
                }
            }
        }
    }

    private void joinGroupWithAutoIntervall(String autoRdvIntervall, Group p_g) {
        int garp = Constants.AUTO_RENDEZVOUS_JOIN_PERIOD;

        try {
            garp = Integer.parseInt(autoRdvIntervall);
        } catch (NumberFormatException nfe) {
            if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE))
                LOG.fine("invalid auto rdv: " + autoRdvIntervall);
        }

        p_g.setVisible(true);
        p_g.setAutoRendezVousPeriod(garp);

        joinGroup(p_g, true, true); // this is the point that can take quite
        // long (authenticator is started)
    }

    private Group createGroupObject(Group parent, String p_gn, String p_gd,
                                    String p_gp, String p_gid) {
        Group g = null;

        try {
            PeerGroup ppg = parent.getPeerGroup();
            PeerGroupAdvertisement pga = PeerGroupUtil.create(ppg, p_gn, p_gd,
                    p_gp, 0, PeerGroupID.create(new URI(p_gid)));

            g = new Group(this, ppg.newGroup(pga), parent);
        } catch (Exception e) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", e);
            }
        }
        return g;
    }

    private void seedGroup(Group parent) {
        String gid = System.getProperty(GROUP_ID, "").trim();
        String gn = System.getProperty(GROUP_NAME, "").trim();
        String gd = System.getProperty(GROUP_DESCRIPTION,
                Defaults.GROUP_DESCRIPTION).trim();
        String autoRdvIntervall = System.getProperty(
                GROUP_AUTO_RENDEZVOUS_PERIOD,
                String.valueOf(Constants.AUTO_RENDEZVOUS_JOIN_PERIOD)).trim();
        String gp = null;

        if (gid.length() > 0) {
            setStatus(STRINGS.getString("status.group.create") + " " + gn);

            Group g = createGroupObject(parent, gn, gd, gp, gid);

            if (g != null) {
                joinGroupWithAutoIntervall(autoRdvIntervall, g);
            } else {
                setStatus(STRINGS.getString("error.group.create") + " " + gn);
            }
        }
    }

    /**
     * Restore settings from previous invocation
     *
     * @param group
     */
    private void restore(Group group) {
        // xxx: restore friends, groups, shares
        PeerGroup pg = group.getPeerGroup();

        setStatus(STRINGS.getString("status.group.restore"));

        for (PeerGroupAdvertisement peerGroupAdvertisement : PeerGroupUtil.getAdvs(pg, null)) {
            GroupNode parentGroupNode = new GroupNode(group);
            GroupNode newGroupNode = new GroupNode(new Group(this,
                    peerGroupAdvertisement, group));
            newGroupNode.setParent(parentGroupNode);
            addJxtaNode(newGroupNode);
        }

        setStatus(STRINGS.getString("status.peer.restore"));

        // String term = Dialog.IMFREE_USER_NAME + Dialog.IMFREE_DELIMITER +
        // SearchModifier.WILDCARD;
        String term = OneToOneCommandDialog.IMFREE_COMMAND_NAME
                + Dialog.IMFREE_DELIMITER + SearchModifier.WILDCARD;

        for (PipeAdvertisement pipeAdvertisement : PipeUtil.getAdvs(pg, term)) {
            PeerNode node = new PeerNode(
                    new Peer(pipeAdvertisement), group);
            node.setParent(new GroupNode(group));
            addJxtaNode(node);
        }
    }

    private void discover(Group group) {
        // if (SearchManager.getInstance().search(Searcher.GROUP, group, this))
        // {
        // setStatus(STRINGS.getString("status.group.discover"));
        // }

        if (SearchManager.getInstance().search(Searcher.PIPE, group, this)) {
            setStatus(STRINGS.getString("status.peer.discover"));
        }
    }

    public PluginContainer getPluginContainer() {
        return m_pluginContainer;
    }

    public ArrayList<String> getAdministrationBuffer() {
        if (administrationBuffer == null)
            administrationBuffer = new ArrayList<String>();
        return administrationBuffer;
    }


    /**
     * rendezvousEvent the rendezvous event
     *
     * @param event rendezvousEvent
     */
    public void rendezvousEvent(RendezvousEvent event) {
        if (event.getType() == RendezvousEvent.RDVCONNECT ||
                event.getType() == RendezvousEvent.RDVRECONNECT ||
                event.getType() == RendezvousEvent.BECAMERDV) {
            synchronized (networkConnectLock) {
                networkConnectLock.notify();
            }
        }
    }

    /**
     * Blocks only, if not connected to a rendezvous, or until a connection to rendezvousNPG node occurs.
     *
     * @param timeout timeout in milliseconds, a zero timeout of waits forever
     * @return true if connected to a rendezvous, false otherwise
     */
    public boolean waitForRendezvousConnection(long timeout) {

        if (0 == timeout) {
            timeout = Long.MAX_VALUE;
        }

        long timeoutAt = System.currentTimeMillis() + timeout;

        if (timeoutAt <= 0) {
            // handle overflow.
            timeoutAt = Long.MAX_VALUE;
        }

        while (!rendezvousNPG.isConnectedToRendezVous() && !rendezvousNPG.isRendezVous()) {
            try {
                long waitFor = timeoutAt - System.currentTimeMillis();

                if (waitFor > 0) {
                    synchronized (networkConnectLock) {
                        networkConnectLock.wait(timeout);
                    }
                } else {
                    // all done with waiting.
                    break;
                }
            } catch (InterruptedException e) {
                Thread.interrupted();
                break;
            }
        }

        return rendezvousNPG.isConnectedToRendezVous() || rendezvousNPG.isRendezVous();
    }
}
